//  -------------------------------------------------------------------------
//  Copyright (C) 2017 BMW Car IT GmbH
//  -------------------------------------------------------------------------
//  This Source Code Form is subject to the terms of the Mozilla Public
//  License, v. 2.0. If a copy of the MPL was not distributed with this
//  file, You can obtain one at https://mozilla.org/MPL/2.0/.
//  -------------------------------------------------------------------------

#include "internal/Core/Utils/PeriodicLogger.h"
#include "internal/Core/Utils/PeriodicLoggerHelper.h"
#include "internal/Core/Utils/LogMacros.h"
#include "ramses-sdk-build-config.h"
#include "internal/PlatformAbstraction/PlatformLock.h"
#include "internal/PlatformAbstraction/PlatformTime.h"

namespace ramses::internal
{
    const uint64_t PeriodicLogger::m_processStartupTime = PlatformTime::GetMillisecondsMonotonic();
    std::atomic<uint32_t> PeriodicLogger::m_numberOfRamsesInstancesStartedInProcess(0);
    std::atomic<uint32_t> PeriodicLogger::m_numberOfRamsesInstancesCurrentlyActive(0);

    PeriodicLogger::PeriodicLogger(PlatformLock& frameworkLock, StatisticCollectionFramework& statisticCollection)
        : m_isRunning(false)
        , m_periodicLogTimeoutSeconds(0)
        , m_thread("PerLogger")
        , m_frameworkLock(frameworkLock)
        , m_statisticCollection(statisticCollection)
        , m_triggerCounter(0)
        , m_previousSteadyTime(std::chrono::steady_clock::now())
        , m_previousSyncTime(synchronized_clock::now())
        , m_ramsesInstanceStartupTime(PlatformTime::GetMillisecondsMonotonic())
    {
        ++m_numberOfRamsesInstancesStartedInProcess;
        ++m_numberOfRamsesInstancesCurrentlyActive;
    }

    PeriodicLogger::~PeriodicLogger()
    {
        m_thread.cancel();
        m_event.signal();
        if (m_isRunning)
        {
            m_thread.join();
        }
        --m_numberOfRamsesInstancesCurrentlyActive;
    }

    void PeriodicLogger::startLogging(uint32_t periodicLogTimeoutSeconds)
    {
        assert(!m_isRunning);
        assert(periodicLogTimeoutSeconds > 0);
        m_periodicLogTimeoutSeconds = periodicLogTimeoutSeconds;
        m_isRunning = true;
        m_statisticCollection.reset();
        m_thread.start(*this);
    }

    void PeriodicLogger::run()
    {
        while (!isCancelRequested())
        {
            m_event.wait(1000);
            m_triggerCounter++;

            {
                PlatformGuard guard(m_frameworkLock);  //guards m_statisticCollectionScenes and m_periodicLogSuppliers

                m_statisticCollection.nextTimeInterval();

                for (auto entry : m_statisticCollectionScenes)
                {
                    entry.value->nextTimeInterval();
                }

                if (m_triggerCounter == m_periodicLogTimeoutSeconds)
                {
                    m_triggerCounter = 0;
                    printVersion();

                    for (auto supplier : m_periodicLogSuppliers)
                    {
                        supplier->triggerLogMessageForPeriodicLog();
                    }

                    printStatistic();
                }
            }
        }
    }

    void PeriodicLogger::registerPeriodicLogSupplier(IPeriodicLogSupplier* supplier)
    {
        PlatformGuard guard(m_frameworkLock);
        m_periodicLogSuppliers.push_back(supplier);
    }

    void PeriodicLogger::removePeriodicLogSupplier(IPeriodicLogSupplier* supplier)
    {
        PlatformGuard guard(m_frameworkLock);

        auto it = find_c(m_periodicLogSuppliers, supplier);
        if (it != m_periodicLogSuppliers.end())
        {
            m_periodicLogSuppliers.erase(it);
        }
    }

    void PeriodicLogger::registerStatisticCollectionScene(const SceneId& sceneId, StatisticCollectionScene& statisticCollectionScene)
    {
        PlatformGuard guard(m_frameworkLock);
        m_statisticCollectionScenes[sceneId] = &statisticCollectionScene;
    }

    void PeriodicLogger::removeStatisticCollectionScene(const SceneId& sceneId)
    {
        PlatformGuard guard(m_frameworkLock);
        m_statisticCollectionScenes.remove(sceneId);
    }

    void PeriodicLogger::printVersion()
    {
        auto steadyNow = std::chrono::steady_clock::now();
        auto syncNow = synchronized_clock::now();

        uint64_t pUp = asMilliseconds(steadyNow) - m_processStartupTime;
        uint64_t rUp = asMilliseconds(steadyNow) - m_ramsesInstanceStartupTime;

        int64_t steadyDiff = std::chrono::duration_cast<std::chrono::milliseconds>(steadyNow - m_previousSteadyTime).count();
        int64_t syncDiff = std::chrono::duration_cast<std::chrono::milliseconds>(syncNow - m_previousSyncTime).count();

        LOG_INFO(CONTEXT_PERIODIC, "Version: {} Hash:{} Commit:{} Type:{} Env:{} SyncT:{}ms (dtSteady:{} - dtSync:{} -> {}) PUp:{} RUp:{} RInit:{} RParallel:{}",
            ::ramses_sdk::RAMSES_SDK_RAMSES_VERSION, ::ramses_sdk::RAMSES_SDK_GIT_COMMIT_HASH, ::ramses_sdk::RAMSES_SDK_GIT_COMMIT_COUNT,
            ::ramses_sdk::RAMSES_SDK_CMAKE_BUILD_TYPE, ::ramses_sdk::RAMSES_SDK_BUILD_ENV_VERSION_INFO_FULL, asMilliseconds(syncNow),
            steadyDiff, syncDiff, steadyDiff - syncDiff, pUp, rUp, m_numberOfRamsesInstancesStartedInProcess.load(), m_numberOfRamsesInstancesCurrentlyActive.load());

        m_previousSyncTime = syncNow;
        m_previousSteadyTime = steadyNow;
    }

    void PeriodicLogger::printStatistic()
    {
        LOG_INFO_F(CONTEXT_PERIODIC, ([&](ramses::internal::StringOutputStream& output) {
                    uint32_t numberTimeIntervals = m_statisticCollection.getNumberTimeIntervalsSinceLastSummaryReset();
                    output << "msgIn ";
                    logStatisticSummaryEntry(output, m_statisticCollection.statMessagesReceived.getSummary(), numberTimeIntervals);
                    output << " msgO ";
                    logStatisticSummaryEntry(output, m_statisticCollection.statMessagesSent.getSummary(), numberTimeIntervals);
                    output << " res+ ";
                    logStatisticSummaryEntry(output, m_statisticCollection.statResourcesCreated.getSummary(), numberTimeIntervals);
                    output << " res- ";
                    logStatisticSummaryEntry(output, m_statisticCollection.statResourcesDestroyed.getSummary(), numberTimeIntervals);
                    output << " resNr ";
                    logStatisticSummaryEntry(output, m_statisticCollection.statResourcesNumber.getSummary(), numberTimeIntervals);
                    output << " resF ";
                    logStatisticSummaryEntry(output, m_statisticCollection.statResourcesLoadedFromFileNumber.getSummary(), numberTimeIntervals);
                    output << " resFS ";
                    logStatisticSummaryEntry(output, m_statisticCollection.statResourcesLoadedFromFileSize.getSummary(), numberTimeIntervals);
        }));

        m_statisticCollection.resetSummaries();

        if (m_statisticCollectionScenes.size() > 0)
        {
            LOG_INFO_F(CONTEXT_PERIODIC, ([&](ramses::internal::StringOutputStream& output) {
                        for (auto entry : m_statisticCollectionScenes)
                        {
                            uint32_t numberTimeIntervals = entry.value->getNumberTimeIntervalsSinceLastSummaryReset();
                            output << "scene: " << entry.key;
                            output << " flush ";
                            logStatisticSummaryEntry(output, entry.value->statFlushesTriggered.getSummary(), numberTimeIntervals);
                            output << " obj+ ";
                            logStatisticSummaryEntry(output, entry.value->statObjectsCreated.getSummary(), numberTimeIntervals);
                            output << " obj- ";
                            logStatisticSummaryEntry(output, entry.value->statObjectsDestroyed.getSummary(), numberTimeIntervals);
                            output << " objNr ";
                            logStatisticSummaryEntry(output, entry.value->statObjectsCount.getSummary(), numberTimeIntervals);
                            output << " actG ";
                            logStatisticSummaryEntry(output, entry.value->statSceneActionsGenerated.getSummary(), numberTimeIntervals);
                            output << " actGS ";
                            logStatisticSummaryEntry(output, entry.value->statSceneActionsGeneratedSize.getSummary(), numberTimeIntervals);
                            output << " actO ";
                            logStatisticSummaryEntry(output, entry.value->statSceneActionsSent.getSummary(), numberTimeIntervals);
                            output << " actSkp ";
                            logStatisticSummaryEntry(output, entry.value->statSceneActionsSentSkipped.getSummary(), numberTimeIntervals);
                            output << " suG ";
                            logStatisticSummaryEntry(output, entry.value->statSceneUpdatesGeneratedPackets.getSummary(), numberTimeIntervals);
                            output << " suGS ";
                            logStatisticSummaryEntry(output, entry.value->statSceneUpdatesGeneratedSize.getSummary(), numberTimeIntervals);
                            output << " suX ";
                            logStatisticSummaryEntry(output, entry.value->statMaximumSizeSingleSceneUpdate.getSummary(), numberTimeIntervals);
                            output << " ar# ";
                            logStatisticSummaryEntry(output, entry.value->statResourceCount[EResourceStatisticIndex_ArrayResource].getSummary(), numberTimeIntervals);
                            output << " aras ";
                            logStatisticSummaryEntry(output, entry.value->statResourceAvgSize[EResourceStatisticIndex_ArrayResource].getSummary(), numberTimeIntervals);
                            output << " arms ";
                            logStatisticSummaryEntry(output, entry.value->statResourceMaxSize[EResourceStatisticIndex_ArrayResource].getSummary(), numberTimeIntervals);
                            output << " er# ";
                            logStatisticSummaryEntry(output, entry.value->statResourceCount[EResourceStatisticIndex_Effect].getSummary(), numberTimeIntervals);
                            output << " eras ";
                            logStatisticSummaryEntry(output, entry.value->statResourceAvgSize[EResourceStatisticIndex_Effect].getSummary(), numberTimeIntervals);
                            output << " erms ";
                            logStatisticSummaryEntry(output, entry.value->statResourceMaxSize[EResourceStatisticIndex_Effect].getSummary(), numberTimeIntervals);
                            output << " tr# ";
                            logStatisticSummaryEntry(output, entry.value->statResourceCount[EResourceStatisticIndex_Texture].getSummary(), numberTimeIntervals);
                            output << " tras ";
                            logStatisticSummaryEntry(output, entry.value->statResourceAvgSize[EResourceStatisticIndex_Texture].getSummary(), numberTimeIntervals);
                            output << " trms ";
                            logStatisticSummaryEntry(output, entry.value->statResourceMaxSize[EResourceStatisticIndex_Texture].getSummary(), numberTimeIntervals);
                            output << " ";

                            entry.value->resetSummaries();
                        }
                    }));
        }
    }

}
